package jmine.tec.performance.test;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

import jmine.tec.utils.debug.TimerCollector;
import jmine.tec.utils.debug.TimerCollector.TimersReading;
import jmine.tec.utils.debug.TimerGroup;
import jmine.tec.utils.debug.impl.DebugRuntimeFactory;
import jmine.tec.utils.debug.impl.TimerImpl.ExecutionResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Data collector for information regarding processing times.
 * 
 * @author joao.enomoto
 */
public class ProcessDataCollector implements DataCollector {

	private static Logger LOG = LoggerFactory.getLogger(ProcessDataCollector.class);

	private static final String PLANILHAS_TIMER = "Planilhas Timer";

	private static Map<String, TimersReading> DELTAS = new TreeMap<String, TimersReading>();

	private TimersReading readings;

	/**
	 * {@inheritDoc}
	 */
	public void startCollect(String name) {
		this.readings = TimerCollector.collectAllReadings();
		TimerGroup timer = DebugRuntimeFactory.getInstance().getOrCreateTimer(PLANILHAS_TIMER);
		timer.start(name);
	}

	/**
	 * {@inheritDoc}
	 */
	public void stopCollect(String name) {
		TimerGroup timer = DebugRuntimeFactory.getInstance().getOrCreateTimer(PLANILHAS_TIMER);
		timer.stop(name);
		TimersReading current = TimerCollector.collectAllReadings();
		TimersReading delta = TimerCollector.computeDelta(this.readings, current);
		LOG.info(name + " - " + timer.getResultAsString(name));
		this.addDelta(this.strip(name), delta);
	}

	/**
	 * {@inheritDoc}
	 */
	public Properties getResultAsProperties() {
		Properties properties = new Properties();
		for (String testName : DELTAS.keySet()) {
			TimersReading delta = DELTAS.get(testName);

			for (String timer : delta.getTimerNames()) {
				String timerPrefix = testName + "." + this.strip(timer);
				ExecutionResult timerSummary = delta.summarize(timer);

				if (timer.equals(PLANILHAS_TIMER)) {
					this.writeResultToProperties(testName, properties, timerSummary);
				} else {
					this.writeResultToProperties(timerPrefix, properties, timerSummary);

					Map<String, ExecutionResult> results = delta.getExecutionResults(timer);
					for (Entry<String, ExecutionResult> entry : results.entrySet()) {
						String reading = entry.getKey();
						ExecutionResult result = entry.getValue();

						String prefix = timerPrefix + "." + this.strip(reading);
						this.writeResultToProperties(prefix, properties, result);
					}
				}
			}
		}
		return properties;
	}

	/**
	 * Write results to parameterized properties object.
	 * 
	 * @param name
	 *            application name.
	 * @param props
	 *            properties object which gathers result information.
	 * @param result
	 *            execution result object.
	 */
	private void writeResultToProperties(String name, Properties props, ExecutionResult result) {
		// TODO joao.enomoto: fazer com que métricas possam ser adicionadas como dependências.
		if (result == null) {
			return;
		}
		props.setProperty(name + ".min", String.valueOf(result.getMin()));
		props.setProperty(name + ".max", String.valueOf(result.getMax()));
		props.setProperty(name + ".mean", String.valueOf(result.getMean()));
		props.setProperty(name + ".sys", String.valueOf(result.getSystemTime()));
		props.setProperty(name + ".cpu", String.valueOf(TimeUnit.NANOSECONDS.toMillis(result.getCpuTime())));
		props.setProperty(name + ".user", String.valueOf(TimeUnit.NANOSECONDS.toMillis(result.getUserTime())));
	}

	/**
	 * Add a delta.
	 * 
	 * @param name
	 *            application name.
	 * @param delta
	 *            timer reading delta.
	 */
	private void addDelta(String name, TimersReading delta) {
		String[] parts = name.split("/");
		String path = "";
		String sep = ".";
		int i = 0;
		do {
			path += parts[i++];
			this.addOrSumDelta(path, delta);
			path += sep;
		} while (parts.length > i);
	}

	/**
	 * Add or sum delta.
	 * 
	 * @param part
	 *            application name.
	 * @param delta
	 *            timer reading delta.
	 */
	private void addOrSumDelta(String part, TimersReading delta) {
		TimersReading stored = null;
		if (DELTAS.containsKey(part)) {
			stored = DELTAS.get(part);
		} else {
			stored = new TimersReading();
		}
		TimerCollector.addReadings(stored, delta);
		DELTAS.put(part, stored);
	}

	/**
	 * @param name
	 *            application name.
	 * @return stripped application name.
	 * @deprecated shouldn't be used, looking for a better way to normalize this name.
	 */
	@Deprecated
	private String strip(String name) {
		return name.replaceAll("(\\.| |%20)", "_");
	}
}
